
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <time.h>

#include "Array.h"
#include "Boolean.h"
#include "ErrorObject.h"
#include "JanusFunction.h"
#include "EsiplyObject.h"
#include "InternalFunction.h"
#include "List.h"
#include "Number.h"
#include "Null.h"
#include "Object.h"
#include "Scope.h"
#include "String.h"
#include "Undefined.h"
#include "Value.h"
#include <stdio.h>
Value* esiply_set_attributes(Value* this_obj, List* args)
{
    // We need at least three arguments.
    if (args->size() < 3)
    {
        return esiply_throw(ET_ERROR, "Esiply.set_attributes given too few args");
    }

    Object* object = args->get(0)->object();
    String* name = args->get(1)->toString();
    Value* attribs = args->get(2);
    unsigned char flags = (unsigned char)attribs->number()->get_value();
    Value* val = object->has(name) ? object->get(name) : Undefined::undefined;
    object->put(name, val, flags);
    return Undefined::undefined;
}

Value* esiply_get_attributes(Value* this_obj, List* args)
{
    // We need at least two arguments
    if (args->size() < 2)
    {
        return esiply_throw(ET_ERROR, "Esiply.get_attributes given too few args");
    }

    Object* object = args->get(0)->object();
    String* name = args->get(1)->toString();
    return new Number((double)object->get_attributes(name));
}

Value* esiply_get_class(Value* this_obj, List* args)
{
    // We need at least one argument
    if (args->empty())
    {
        return esiply_throw(ET_ERROR, "Esiply.get_class given too few args");
    }

    Object* object = args->get(0)->toObject();
    return object->get_class();
}

Value* esiply_set_class(Value* this_obj, List* args)
{
    // We need at least two argument
    if (args->size() < 2)
    {
        return esiply_throw(ET_ERROR, "Esiply.set_class given too few args");
    }

    Object* object = args->get(0)->object();
    String* clss = args->get(1)->toString();
    object->set_class(clss);
    return Undefined::undefined;
}

Value* esiply_get_prototype(Value* this_obj, List* args)
{
    // We need at least one argument
    if (args->empty())
    {
        return esiply_throw(ET_ERROR, "Esiply.get_prototype given too few args");
    }

    Object* object = args->get(0)->object();
    Value* proto = object->get_prototype();
    return proto ? proto : Null::null;
}

Value* esiply_set_prototype(Value* this_obj, List* args)
{
    // We need at least two argument
    if (args->size() < 2)
    {
        return esiply_throw(ET_ERROR, "Esiply.set_prototype given too few args");
    }

    Object* object = args->get(0)->object();
    Object* proto = args->get(1)->toObject();
    object->set_prototype(proto);
    return Undefined::undefined;
}

Value* esiply_get_value(Value* this_obj, List* args)
{
    // We need at least one argument
    if (args->empty())
    {
        return esiply_throw(ET_ERROR, "Esiply.get_value given too few args");
    }

    Object* object = args->get(0)->object();
    return object->get_value();
}

Value* esiply_set_value(Value* this_obj, List* args)
{
    // We need at least two argument
    if (args->size() < 2)
    {
        return esiply_throw(ET_ERROR, "Esiply.set_value given too few args");
    }

    Object* object = args->get(0)->object();
    Value* value = args->get(1);
    object->set_value(value);
    return Undefined::undefined;
}

Value* esiply_object_has(Value* this_obj, List* args)
{
    // We need at least two argument
    if (args->size() < 2)
    {
        return esiply_throw(ET_ERROR, "Esiply.object_has given too few args");
    }

    Object* object = args->get(0)->object();
    String* name = args->get(1)->string();
    return object->has(name) ? Boolean::True : Boolean::False;
}

Value* esiply_create_janus_function(Value* this_obj, List* args)
{
    // We need at least two arguments
    if (args->size() < 2)
    {
        return esiply_throw(ET_ERROR,
                "Esiply.create_janus_function given too few args");
    }

    Function* call = args->get(0)->function();
    Function* construct = args->get(1)->function();
    return new JanusFunction(call, construct);
}

Value* esiply_to_object(Value* this_obj, List* args)
{
    // We need at least one arguments
    if (args->empty())
    {
        return esiply_throw(ET_ERROR,
                "Esiply.to_object given too few args");
    }

    Value* value = args->get(0);
    return value->toObject();
}

Value* esiply_to_string(Value* this_obj, List* args)
{
    // We need at least one arguments
    if (args->empty())
    {
        return esiply_throw(ET_ERROR,
                "Esiply.to_string given too few args");
    }

    Value* value = args->get(0);
    return value->toString();
}


Value* esiply_to_primitive(Value* this_obj, List* args)
{
    // We need at least one arguments
    if (args->empty())
    {
        return esiply_throw(ET_ERROR,
                "Esiply.to_primitive given too few args");
    }

    Value* value = args->get(0);
    return value->toPrimitive();
}

Value* esiply_get_current_time(Value* this_obj, List* args)
{
    // TODO: use gettimeofday instead of time.
    Number* foo = new Number(1000.0 * (double)time(NULL));
    fprintf(stderr, "TIME: %f\n", foo->get_value());
    return foo;
}

Value* esiply_daylight_saving_ta(Value* this_obj, List* args)
{
    // We need at least one arguments
    if (args->empty())
    {
        return esiply_throw(ET_ERROR,
                "Esiply.daylight_saving_ta given too few args");
    }

    // TODO: actually do something useful here.
    return new Number(0);
}

Value* esiply_charCodeAt(Value* this_obj, List* args)
{
    // Convert the first argument to a string. The second is the index
    // to get the charCodeAt from
    // We need at least two arguments
    if (args->size() < 2)
    {
        return esiply_throw(ET_ERROR,
                "Esiply.charCodeAt given too few args");
    }

    String* str = args->get(0)->toString();
    Number* pos = args->get(1)->toNumber();
    return str->charCodeAt((int)(pos->get_value()));
}

Value* esiply_charFromCode(Value* this_obj, List* args)
{
    // Return a string made up of the single character which is the
    // translation of the first argument from a 16 bit code point value
    // to a character
    if (args->empty())
    {
        return esiply_throw(ET_ERROR,
                "Esiply.charFromCode given too few args");
    }

    Number* code = args->get(0)->toUint16();
    return new String((int)code->get_value());
}

Value* esiply_string_length(Value* this_obj, List* args)
{
    // We need at least one arguments
    if (args->empty())
    {
        return esiply_throw(ET_ERROR,
                "Esiply.to_string given too few args");
    }

    String* str = args->get(0)->toString();
    return new Number((double)str->length());
}

void esiply_object_init(Scope* scope)
{
    // The Esiply object
    Object* esiply = new Object();
    scope->get_variable()->put(new String("Esiply"), esiply, PA_EMPTY);

    // The Attribute flags
    esiply->put(new String("DONT_ENUM"), new Number((double)PA_DONT_ENUM),
            PA_EMPTY);
    esiply->put(new String("READ_ONLY"), new Number((double)PA_READ_ONLY),
            PA_EMPTY);
    esiply->put(new String("DONT_DELETE"), new Number((double)PA_DONT_DELETE),
            PA_EMPTY);

    // The set_attributes function
    esiply->put(new String("set_attributes"),
            new InternalFunction(esiply_set_attributes, NULL, 3), PA_EMPTY);

    esiply->put(new String("get_attributes"),
            new InternalFunction(esiply_get_attributes, NULL, 2), PA_EMPTY);

    esiply->put(new String("get_class"),
            new InternalFunction(esiply_get_class, NULL, 1), PA_EMPTY);

    esiply->put(new String("set_class"),
            new InternalFunction(esiply_set_class, NULL, 2), PA_EMPTY);

    esiply->put(new String("get_prototype"),
            new InternalFunction(esiply_get_prototype, NULL, 1), PA_EMPTY);

    esiply->put(new String("set_prototype"),
            new InternalFunction(esiply_set_prototype, NULL, 2), PA_EMPTY);

    esiply->put(new String("get_value"),
            new InternalFunction(esiply_get_value, NULL, 1), PA_EMPTY);

    esiply->put(new String("set_value"),
            new InternalFunction(esiply_set_value, NULL, 2), PA_EMPTY);

    esiply->put(new String("object_has"),
            new InternalFunction(esiply_object_has, NULL, 2), PA_EMPTY);

    esiply->put(new String("create_janus_function"),
            new InternalFunction(esiply_create_janus_function, NULL, 2),
            PA_EMPTY);

    esiply->put(new String("to_object"),
            new InternalFunction(esiply_to_object, NULL, 1), PA_EMPTY);

    esiply->put(new String("to_string"),
            new InternalFunction(esiply_to_string, NULL, 1), PA_EMPTY);

    esiply->put(new String("to_primitive"),
            new InternalFunction(esiply_to_primitive, NULL, 1), PA_EMPTY);

    esiply->put(new String("get_current_time"),
            new InternalFunction(esiply_get_current_time, NULL, 0), PA_EMPTY);

    esiply->put(new String("charCodeAt"),
            new InternalFunction(esiply_charCodeAt, NULL, 2), PA_EMPTY);

    esiply->put(new String("charFromCode"),
            new InternalFunction(esiply_charFromCode, NULL, 1), PA_EMPTY);

    esiply->put(new String("string_length"),
            new InternalFunction(esiply_string_length, NULL, 1), PA_EMPTY);

    esiply->put(new String("array_prototype"), Array::prototype, PA_EMPTY);
    esiply->put(new String("boolean_prototype"), Boolean::prototype, PA_EMPTY);
    esiply->put(new String("function_prototype"),Function::prototype, PA_EMPTY);
    esiply->put(new String("number_prototype"), Number::prototype, PA_EMPTY);
    esiply->put(new String("string_prototype"), String::prototype, PA_EMPTY);

    // TODO: get the right number here
    tzset();
    esiply->put(new String("local_tza"), new Number((double)(-timezone*1000)),
                PA_EMPTY);

    esiply->put(new String("daylight_saving_ta"),
            new InternalFunction(esiply_daylight_saving_ta, NULL, 1), PA_EMPTY);
}

